/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.muni.fi.diplomka.datalayer.daoimpl;

import cz.fi.muni.diplomka.common.Army;
import cz.fi.muni.diplomka.common.Squad;
import cz.fi.muni.diplomka.common.Unit;
import cz.fi.muni.diplomka.common.enumeration.UnitType;
import cz.muni.fi.diplomka.datalayer.dao.CityArmyDao;
import cz.muni.fi.diplomka.datalayer.entity.CityArmyEntity;
import cz.muni.fi.diplomka.datalayer.util.DbConnector;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.neo4j.cypher.javacompat.ExecutionEngine;
import org.neo4j.cypher.javacompat.ExecutionResult;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.ResourceIterator;
import org.neo4j.graphdb.Transaction;

/**
 *
 * @author Jakub Kutil
 */
public class CityArmyDaoImpl implements CityArmyDao {

    @Override
    public void addArmy(CityArmyEntity cityArmyEntity) {
        if (cityArmyEntity == null) {
            throw new IllegalArgumentException("City Army Entity can not be null.");
        }

        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();

        try (Transaction t = gds.beginTx()) {

            Map<String, Object> params = new HashMap<>();

            String query = "MATCH (CityArmy:CityArmy) RETURN max(CityArmy.id)";

            Long key = new Long(0);
            ExecutionResult result = engine.execute(query);
            Iterator<Long> number = result.columnAs("max(CityArmy.id)");
            while (number.hasNext()) {
                key = number.next();
            }

            if (key == null) {
                key = new Long(0);
            }
            cityArmyEntity.setId(new Long(key + 1));

            params.put("id", cityArmyEntity.getId());
            params.put("cityId", cityArmyEntity.getCityId());
            params.put("ownerCityId", cityArmyEntity.getOwnerCityId());
            params.put("name", cityArmyEntity.getName());
            params.put("guard", cityArmyEntity.isGuard());

            query = "MATCH (OwnerCity:City {id :{ownerCityId}}) "
                    + "CREATE (CityArmy:CityArmy {id : {id}, name : {name}, guard : {guard}})<-[r:COMMANDS]-(OwnerCity) ";                 
            engine.execute(query, params);

            query = "MATCH (CityArmy:CityArmy {id : {id}}), (City:City {id :{cityId}}) "
                    + "CREATE (CityArmy)<-[r2:STATIONED]-(City)";
            engine.execute(query, params);
            for (Squad squad : cityArmyEntity.getArmy().getSquads()) {

                params.put("name", squad.getUnit().getName().toString());
                params.put("position", squad.getPosition());
                params.put("count", squad.getCount());
                params.put("threshold", squad.getThreshold());

                query = "MATCH (CityArmy:CityArmy {id :{id}}) "
                        + "CREATE (CityArmyUnits:CityArmyUnits {name : {name}, position : {position}, count : {count}, "
                        + "threshold : {threshold}})<-[r:HAS]-(CityArmy)";
                engine.execute(query, params);
            }
            t.success();
        }
    }

    @Override
    public void editArmy(CityArmyEntity cityArmyEntity) {
        if (cityArmyEntity == null) {
            throw new IllegalArgumentException("City Army Entity can not be null.");
        }

        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();

        try (Transaction t = gds.beginTx()) {

            Map<String, Object> params = new HashMap<>();

            params.put("id", cityArmyEntity.getId());
            params.put("cityId", cityArmyEntity.getCityId());
            params.put("ownerCityId", cityArmyEntity.getOwnerCityId());
            params.put("name", cityArmyEntity.getName());
            params.put("guard", cityArmyEntity.isGuard());

            String query = "MATCH (CityArmy:CityArmy {id :{id}}) "
                    + "SET CityArmy.name = {name}, CityArmy.guard = {guard}";
            engine.execute(query, params);

            query = "MATCH (CityArmy:CityArmy {id :{id}})<-[r:STATIONED]-(City:City)"
                    + " DELETE r";
            engine.execute(query, params);

            query = "MATCH (CityArmy:CityArmy {id :{id}}), (City:City {id: {cityId}})"
                    + " CREATE (CityArmy)<-[r:STATIONED]-(City)";
            engine.execute(query, params);

            for (Squad squad : cityArmyEntity.getArmy().getSquads()) {

                params.put("name", squad.getUnit().getName().toString());
                params.put("position", squad.getPosition());
                params.put("count", squad.getCount());
                params.put("threshold", squad.getThreshold());

                query = "MATCH (CityArmy:CityArmy {id :{id}})-[r:HAS]->(CityArmyUnits:CityArmyUnits { position : {position}}) "
                        + "SET CityArmyUnits.name = {name}, CityArmyUnits.count = {count}, CityArmyUnits.threshold= {threshold}";
                engine.execute(query, params);
            }
            t.success();
        }
    }

    @Override
    public void deleteArmy(Long id) {
        if (id == null) {
            throw new IllegalArgumentException("Id can not be null.");
        }

        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();

        try (Transaction t = gds.beginTx()) {

            Map<String, Object> params = new HashMap<>();

            params.put("id", id);

            String query = "MATCH ()-[r]-(CityArmy:CityArmy{ id : {id}})-[r2:HAS]->(CityArmyUnits:CityArmyUnits) "
                    + "DELETE r,r2,CityArmyUnits,CityArmy";
            engine.execute(query, params);

            t.success();
        }
    }

    @Override
    public List<CityArmyEntity> getArmiesInCity(Long cityId) {
        if (cityId == null) {
            throw new IllegalArgumentException("City Id can not be null.");
        }
        List<CityArmyEntity> armyList = new ArrayList<>();
        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();


        try (Transaction t = gds.beginTx()) {
            Map<String, Object> params = new HashMap<>();

            params.put("id", cityId);

            String query = "MATCH (City:City {id : {id}})-[r:STATIONED]->(CityArmy:CityArmy)<-[r2:COMMANDS]-(CityOwner:City) "
                    + "RETURN CityArmy,CityOwner.id";

            ExecutionResult result = engine.execute(query, params);

            ResourceIterator<Map<String, Object>> armies = result.iterator();

            while (armies.hasNext()) {
                Map<String, Object> row = armies.next();
                Node node = (Node) row.get("CityArmy");

                CityArmyEntity cityArmyEntity = new CityArmyEntity();
                cityArmyEntity.setId(new Long(node.getProperty("id").toString()));
                cityArmyEntity.setCityId(cityId);
                cityArmyEntity.setOwnerCityId(new Long(row.get("CityOwner.id").toString()));
                cityArmyEntity.setName(node.getProperty("name").toString());
                cityArmyEntity.setGuard(Boolean.valueOf(node.getProperty("guard").toString()));

                armyList.add(cityArmyEntity);
            }
            for (CityArmyEntity cityArmyEntity : armyList) {
                List<Squad> squads = new ArrayList<>();
                params.clear();
                params.put("id", cityArmyEntity.getId());

                query = "MATCH (CityArmy:CityArmy {id : {id}})-[r:HAS]->(CityArmyUnits:CityArmyUnits) "
                        + "RETURN CityArmyUnits";

                result = engine.execute(query, params);

                Iterator<Node> units = result.columnAs("CityArmyUnits");
                while (units.hasNext()) {
                    Node node = units.next();
                    Squad squad = new Squad();
                    squad.setCount(Float.valueOf(node.getProperty("count").toString()));
                    squad.setPosition(Integer.valueOf(node.getProperty("position").toString()));
                    squad.setThreshold(Float.valueOf(node.getProperty("threshold").toString()));
                    Unit unit = new Unit();
                    unit.setName(UnitType.valueOf(node.getProperty("name").toString()));
                    squad.setUnit(unit);

                    squads.add(squad);
                }
                Army army = new Army();
                army.setSquads(squads);
                cityArmyEntity.setArmy(army);

            }
            t.success();
        }
        return armyList;
    }

    @Override
    public CityArmyEntity getArmyOnWallsInCity(Long cityId) {
        if (cityId == null) {
            throw new IllegalArgumentException("City Id can not be null.");
        }

        CityArmyEntity cityArmyEntity = new CityArmyEntity();

        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();


        try (Transaction t = gds.beginTx()) {
            Map<String, Object> params = new HashMap<>();

            params.put("id", cityId);

            String query = "MATCH (City:City {id : {id}})-[r:STATIONED]->(CityArmy:CityArmy {guard : true})<-[r2:COMMANDS]-(OwnerCity:City) "
                    + "RETURN CityArmy,OwnerCity.id";

            ExecutionResult result = engine.execute(query, params);

            ResourceIterator<Map<String, Object>> armies = result.iterator();

            while (armies.hasNext()) {
                Map<String, Object> row = armies.next();
                Node node = (Node) row.get("CityArmy");

                cityArmyEntity.setId(new Long(node.getProperty("id").toString()));
                cityArmyEntity.setCityId(cityId);
                cityArmyEntity.setOwnerCityId(new Long(row.get("OwnerCity.id").toString()));
                cityArmyEntity.setName(node.getProperty("name").toString());
                cityArmyEntity.setGuard(Boolean.valueOf(node.getProperty("guard").toString()));

            }
            List<Squad> squads = new ArrayList<>();
            params.clear();
            params.put("id", cityArmyEntity.getId());

            query = "MATCH (CityArmy:CityArmy {id : {id}})-[r:HAS]->(CityArmyUnits:CityArmyUnits) "
                    + "RETURN CityArmyUnits";

            result = engine.execute(query, params);

            Iterator<Node> units = result.columnAs("CityArmyUnits");
            while (units.hasNext()) {
                Node node = units.next();
                Squad squad = new Squad();
                squad.setCount(Float.valueOf(node.getProperty("count").toString()));
                squad.setPosition(Integer.valueOf(node.getProperty("position").toString()));
                squad.setThreshold(Float.valueOf(node.getProperty("threshold").toString()));
                Unit unit = new Unit();
                unit.setName(UnitType.valueOf(node.getProperty("name").toString()));
                squad.setUnit(unit);

                squads.add(squad);
            }
            Army army = new Army();

            army.setSquads(squads);
            cityArmyEntity.setArmy(army);

            t.success();
        }
        return cityArmyEntity;
    }

    @Override
    public List<CityArmyEntity> getAwayArmiesFromCity(Long cityId) {
        if (cityId == null) {
            throw new IllegalArgumentException("City Id can not be null.");
        }
        List<CityArmyEntity> armyList = new ArrayList<>();
        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();


        try (Transaction t = gds.beginTx()) {
            Map<String, Object> params = new HashMap<>();

            params.put("id", cityId);

            String query = "MATCH (City:City {id : {id}})-[r:STATIONED]->(CityArmy:CityArmy)<-[r2:COMMANDS]-(CityOwner:City) WHERE CityOwner.id <> {id}"
                    + "RETURN CityArmy,CityOwner.id";

            ExecutionResult result = engine.execute(query, params);

            ResourceIterator<Map<String, Object>> armies = result.iterator();

            while (armies.hasNext()) {
                Map<String, Object> row = armies.next();
                Node node = (Node) row.get("CityArmy");

                CityArmyEntity cityArmyEntity = new CityArmyEntity();
                cityArmyEntity.setId(new Long(node.getProperty("id").toString()));
                cityArmyEntity.setCityId(cityId);
                cityArmyEntity.setOwnerCityId(new Long(row.get("CityOwner.id").toString()));
                cityArmyEntity.setName(node.getProperty("name").toString());
                cityArmyEntity.setGuard(Boolean.valueOf(node.getProperty("guard").toString()));

                armyList.add(cityArmyEntity);
            }
            for (CityArmyEntity cityArmyEntity : armyList) {
                List<Squad> squads = new ArrayList<>();
                params.clear();
                params.put("id", cityArmyEntity.getId());

                query = "MATCH (CityArmy:CityArmy {id : {id}})-[r:HAS]->(CityArmyUnits:CityArmyUnits) "
                        + "RETURN CityArmyUnits";

                result = engine.execute(query, params);

                Iterator<Node> units = result.columnAs("CityArmyUnits");
                while (units.hasNext()) {
                    Node node = units.next();
                    Squad squad = new Squad();
                    squad.setCount(Float.valueOf(node.getProperty("count").toString()));
                    squad.setPosition(Integer.valueOf(node.getProperty("position").toString()));
                    squad.setThreshold(Float.valueOf(node.getProperty("threshold").toString()));
                    Unit unit = new Unit();
                    unit.setName(UnitType.valueOf(node.getProperty("name").toString()));
                    squad.setUnit(unit);

                    squads.add(squad);
                }
                Army army = new Army();
                army.setSquads(squads);
                cityArmyEntity.setArmy(army);

            }
            t.success();
        }
        return armyList;
    }
}
